#include "core/main.h"

Bitmap::Bitmap()
{
    _data = 0;
    _width = 0;
    _height = 0;
}

Bitmap::Bitmap(UINT Width, UINT Height)
{
    _data = 0;
    Allocate(Width, Height);
}

Bitmap::Bitmap(UINT Width, UINT Height, RGBColor clearColor)
{
    _data = 0;
    Allocate(Width, Height);
    Clear(clearColor);
}

Bitmap::~Bitmap()
{
    FreeMemory();
}

Bitmap::Bitmap(const Bitmap &B)
{
    _data = NULL;
    Allocate(B._width,B._height);
    memcpy(_data,B._data,sizeof(RGBColor)*_width*_height);
}

Bitmap::Bitmap(Bitmap &&B)
{
    _width = B._width;
    _height = B._height;
    _data = B._data;
    B._width = 0;
    B._height = 0;
    B._data = NULL;
}

void Bitmap::operator = (const Bitmap &B)
{
    if(&B == this) return;
    Allocate(B._width,B._height);
    memcpy(_data,B._data,sizeof(RGBColor)*_width*_height);
}

void Bitmap::operator = (Bitmap &&Bmp)
{
    if(this != &Bmp)
    {
        if(_data != NULL)
        {
            delete[] _data;
        }
        _width = Bmp._width;
        _height = Bmp._height;
        _data = Bmp._data;
        Bmp._width = 0;
        Bmp._height = 0;
        Bmp._data = NULL;
    }
}

void Bitmap::FreeMemory()
{
    if(_data)
    {
        delete[] _data;
    }
    _data = NULL;
    _width = 0;
    _height = 0;
}

void Bitmap::Allocate(UINT Width, UINT Height)
{
    if(_data == NULL || _width != Width || _height != Height)
    {
        FreeMemory();
        _width = Width;
        _height = Height;
        _data = new RGBColor[Width * Height];
    }
}

void Bitmap::Allocate(UINT Width, UINT Height, RGBColor clearColor)
{
    Allocate(Width, Height);
    Clear(clearColor);
}

void Bitmap::PNGFlushBuffer(png_structp png_ptr)
{
    
}

void Bitmap::LoadPNG(const string &filename)
{
    FILE *File;
    File = fopen(filename.c_str(), "rb");
    Assert(File != NULL, string("File open for LoadPNG failed: ") + filename);

    png_structp PngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    Assert(PngRead != NULL, "png_create_read_struct failed.");

    png_infop PngInfo = png_create_info_struct(PngRead);
    Assert(PngInfo != NULL, "png_create_info_struct failed.");

    png_init_io(PngRead, File);
    PNGCompleteRead(PngRead, PngInfo, filename);

    fclose(File);
}

void Bitmap::PNGCompleteRead(png_structp PngRead, png_infop PngInfo, const string &Filename)
{
    png_read_png(PngRead, PngInfo, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
    png_bytepp RowPointers = png_get_rows(PngRead, PngInfo);
    //Assert(RowPointers != NULL, "png_get_rows failed.");

    Allocate(PngRead->width, PngRead->height);
    if(PngRead->channels == 3 && PngRead->pixel_depth == 24)
    {
        for(UINT y = 0; y < _height; y++)
        {
            png_bytep CurRow = RowPointers[y];
            for(UINT x = 0; x < _width; x++)
            {
                (*this)[y][x].r = CurRow[x * 3 + 0];
                (*this)[y][x].g = CurRow[x * 3 + 1];
                (*this)[y][x].b = CurRow[x * 3 + 2];
                (*this)[y][x].a = 0;
            }
        }
    }
    else if(PngRead->channels == 4 && PngRead->pixel_depth == 32)
    {
        for(UINT y = 0; y < _height; y++)
        {
            png_bytep CurRow = RowPointers[y];
            for(UINT x = 0; x < _width; x++)
            {
                (*this)[y][x].r = CurRow[x * 4 + 0];
                (*this)[y][x].g = CurRow[x * 4 + 1];
                (*this)[y][x].b = CurRow[x * 4 + 2];
                (*this)[y][x].a = CurRow[x * 4 + 3];
            }
        }
    }
    else if(PngRead->channels == 1 && PngRead->pixel_depth == 8)
    {
        for(UINT y = 0; y < _height; y++)
        {
            png_bytep CurRow = RowPointers[y];
            for(UINT x = 0; x < _width; x++)
            {
                BYTE C = CurRow[x];
                (*this)[y][x] = RGBColor(C, C, C, 0);
            }
        }
    }
    else
    {
        cout << "Unsupported number of channels" << endl;
        Clear(RGBColor::Magenta);
    }

    png_destroy_read_struct(&PngRead, &PngInfo, NULL);
}

void Bitmap::FlipBlueAndRed()
{
    for(UINT y = 0; y < _height; y++)
    {
        for(UINT x = 0; x < _width; x++)
        {
            RGBColor &c = (*this)[y][x];
            unsigned char temp = c.r;
            c.r = c.b;
            c.b = temp;
        }
    }
}

void Bitmap::SavePNG(const string &Filename) const
{
    //PersistentAssert(_width > 0 && _height > 0, "Saving empty image");

    FILE *File;
    File = fopen(Filename.c_str(), "wb");
    //PersistentAssert(File != NULL, string("File open for SavePNG failed: ") + Filename);

    png_structp PngWrite = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    //PersistentAssert(PngWrite != NULL, "png_create_write_struct failed.");

    png_infop PngInfo = png_create_info_struct(PngWrite);
    //PersistentAssert(PngInfo != NULL, "png_create_info_struct failed.");

    png_init_io(PngWrite, File);

    PNGCompleteWrite(PngWrite, PngInfo);

    fclose(File);
}

void Bitmap::PNGCompleteWrite(png_structp PngWrite, png_infop PngInfo) const
{
    UINT LocalWidth = _width;
    UINT LocalHeight = _height;

    png_set_IHDR(PngWrite, PngInfo, LocalWidth, LocalHeight, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    png_set_PLTE(PngWrite, PngInfo, NULL, 0);
    png_set_gAMA(PngWrite, PngInfo, 0.0);
    //png_set_sRGB_gAMA_and_cHRM(PngWrite, PngInfo, PNG_sRGB_INTENT_PERCEPTUAL);
    
    png_color_8 ColorInfo;

    ColorInfo.alpha = 0;
    UINT BytesPerPixel = 3;
    ColorInfo.red = 8;
    ColorInfo.green = 8;
    ColorInfo.blue = 8;
    ColorInfo.gray = 0;
    png_set_sBIT(PngWrite, PngInfo, &ColorInfo);
    
    
    UINT ScratchSizeNeeded = (sizeof(png_bytep) + sizeof(png_byte) * BytesPerPixel * LocalWidth) * LocalHeight;
    BYTE *ScratchSpace = new BYTE[ScratchSizeNeeded];
    BYTE *ScratchSpaceStart = ScratchSpace;

    png_bytep *RowPointers = (png_bytep *)ScratchSpace;
    ScratchSpace += sizeof(png_bytep) * LocalHeight;
    for(UINT y = 0; y < LocalHeight; y++)
    {
        BYTE *DestRowStart = ScratchSpace;
        ScratchSpace += sizeof(png_byte) * BytesPerPixel * LocalWidth;
        RowPointers[LocalHeight - 1 - y] = DestRowStart;
        
        const RGBColor *SrcRowStart = (*this)[y];
        UINT DestRowStartOffset = 0;
        
        for(UINT x = 0; x < LocalWidth; x++)
        {
            RGBColor Color = SrcRowStart[x];
            DestRowStart[DestRowStartOffset++] = Color.r;
            DestRowStart[DestRowStartOffset++] = Color.g;
            DestRowStart[DestRowStartOffset++] = Color.b;
        }
    }

    png_set_rows(PngWrite, PngInfo, RowPointers);
    png_write_png(PngWrite, PngInfo, NULL, NULL);

    png_destroy_write_struct(&PngWrite, &PngInfo);

    if(ScratchSpaceStart != NULL)
    {
        delete[] ScratchSpaceStart;
    }
}

void Bitmap::Clear()
{
    memset(_data, 0, sizeof(RGBColor) * _width * _height);
}

void Bitmap::Clear(const RGBColor &Color)
{
    for(UINT i=0;i<_width;i++)
    {
        _data[i] = Color;
    }

    for(UINT i2=1;i2<_height;i2++)
    {
        memcpy(&_data[i2*_width], &_data[0], sizeof(RGBColor) * _width);
    }
}

void Bitmap::BltTo(Bitmap &B, int TargetX, int TargetY) const
{
    BltTo(B, TargetX, TargetY, 0, 0, _width, _height);
}

void Bitmap::BltTo(Bitmap &B, int TargetX, int TargetY, int SourceX, int SourceY, int Width, int Height) const
{
    const int BHeight = B._height;
    const int BWidth = B._width;
    for(int y = 0; y < Height; y++)
    {
        const int CurTargetY = y + TargetY;
        const int CurSourceY = y + SourceY;
        if(CurTargetY >= 0 && CurTargetY < BHeight &&
           CurSourceY >= 0 && CurSourceY < int(_height))
        {
            for(int x = 0; x < Width; x++)
            {
                const int CurTargetX = x + TargetX;
                const int CurSourceX = x + SourceX;
                if(CurTargetX >= 0 && CurTargetX < BWidth &&
                   CurSourceX >= 0 && CurSourceX < int(_width))
                {
                    B[CurTargetY][CurTargetX] = (*this)[CurSourceY][CurSourceX];
                }
            }
        }
    }
}

void Bitmap::TransparentBltTo(Bitmap &B, int TargetX, int TargetY) const
{
    const int BHeight = B._height;
    const int BWidth = B._width;
    for(int y = 0; y < int(_height); y++)
    {
        const int CurTargetY = y + TargetY;
        const int CurSourceY = y;
        if(CurTargetY >= 0 && CurTargetY < BHeight &&
           CurSourceY >= 0 && CurSourceY < int(_height))
        {
            for(int x = 0; x < int(_width); x++)
            {
                const int CurTargetX = x + TargetX;
                const int CurSourceX = x;
                if(CurTargetX >= 0 && CurTargetX < BWidth &&
                   CurSourceX >= 0 && CurSourceX < int(_width))
                {
                    RGBColor myColor = (*this)[CurSourceY][CurSourceX];
                    if(myColor != RGBColor::Magenta)
                    {
                        B[CurTargetY][CurTargetX] = myColor;
                    }
                }
            }
        }
    }
}

UINT32 Bitmap::Hash32() const
{
    return Utility::Hash32((BYTE *)_data, sizeof(RGBColor) * _width * _height) + (_width * 785 + _height * 97);
}

UINT64 Bitmap::Hash64() const
{
    return Utility::Hash64((BYTE *)_data, sizeof(RGBColor) * _width * _height) + (_width * 785 + _height * 97);
}

bool Bitmap::PerfectMatch(const Bitmap &Bmp) const
{
    if(Bmp.Dimensions() != Dimensions())
    {
        return false;
    }
    for(UINT y = 0; y < _height; y++)
    {
        for(UINT x = 0; x < _width; x++)
        {
            if ( (*this)[y][x].r != Bmp[y][x].r ||
                 (*this)[y][x].g != Bmp[y][x].g ||
                 (*this)[y][x].b != Bmp[y][x].b)
            {
                return false;
            }
        }
    }
    return true;
}

void Bitmap::FlipHorizontal()
{
    Bitmap BTemp = *this;
    for(UINT y = 0; y < _height; y++)
    {
        for(UINT x = 0; x < _width; x++)
        {
            (*this)[y][x] = BTemp[y][_width - 1 - x];
        }
    }
}

void Bitmap::FlipVertical()
{
    Bitmap BTemp = *this;
    for(UINT y = 0; y < _height; y++)
    {
        for(UINT x = 0; x < _width; x++)
        {
            (*this)[y][x] = BTemp[_height - 1 - y][x];
        }
    }
}

OutputDataStream& operator << (OutputDataStream &stream, const Bitmap &bmp)
{
    stream << bmp.Width() << bmp.Height();
    stream.WriteData((BYTE *)bmp.Pixels(), bmp.PixelCount() * sizeof(RGBColor));
    return stream;
}

InputDataStream& operator >> (InputDataStream &stream, Bitmap &bmp)
{
    UINT width, height;
    stream >> width >> height;
    bmp.Allocate(width, height);
    stream.ReadData((BYTE *)bmp.Pixels(), bmp.PixelCount() * sizeof(RGBColor));
    return stream;
}

Bitmap Bitmap::LoadFile(const string &filename)
{
    Bitmap result;
    result.LoadPNG(filename);
    return result;
}

UINT Bitmap::CountPixelsWithColor(RGBColor Color) const
{
    UINT result = 0;
    for(UINT y = 0; y < _height; y++)
    {
        for(UINT x = 0; x < _width; x++)
        {
            if(_data[y * _width + x] == Color)
            {
                result++;
            }
        }
    }
    return result;
}